package com.zx.练习题._2020面试题;

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;

/**
 * 【Boos直聘】【编码机试】long access(String ip) 返回ip被调用的次数
 * 比如: ip = a, 调用多次后返回的值是1,2,3,4..，之后ip=b调用多次，返回值一次是1,2,3,4
 */
@Slf4j
public class ip被调用的次数 {

    private static final ReentrantLock lock = new ReentrantLock();

    // 这里为什么不使用HashMap。虽然map写入操作使用lock内完成，但是HashMap无法保证多线程之间的可见性。
    // 写入虽然做了双重校验，但是可见性无法保证，再多校验也没有用，会导致旧的数据丢失。
    private static ConcurrentHashMap<String, AtomicLong> map = new ConcurrentHashMap<>();

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newCachedThreadPool();
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            final String ip = "192.168.0." + random.nextInt(10);
            pool.execute(() -> {
                long access = access(ip);
                log.info("ip = {}, access = {}", ip, access);
            });
        }
        while (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
            pool.shutdown();
        }
    }

    private static long access(String ip) {
        AtomicLong counter = map.get(ip);
        if (counter != null) {
            return counter.incrementAndGet();
        }
        // 初始化数据的过程需要锁
        lock.lock();
        // 只有不存的时候需要加锁添加数据，所有对于map，是不存在线程安全的
        try {
            // 双重校验
            if (map.contains(ip)) {
                counter = map.get(ip);
            } else {
                counter = new AtomicLong(0);
                map.put(ip, counter);

            }
        } catch (Exception e) {
            log.error("", e);
        } finally {
            lock.unlock();
        }

        return counter.incrementAndGet();
    }
}
